#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <pthread.h>
#include <unistd.h>
#include <errno.h>
#include <ctype.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <strings.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/wait.h>

# define SERV_PORT 9527

void sys_err(const char *str)
{
    perror(str);
    exit(1);
}

//优化select创建的服务器
int main(int argc, char *argv[])
{
	int lfd, cfd;
	int i ,j, fret, nready;
	int maxfd = 0;
	char buf[4096], clit_ip[1024];
	int client[1024];//自定义一个cfd数组，防止每次访问都要遍历，提高效率
	int maxi = -1;//client[]最后一个元素的下标

	struct sockaddr_in	serv_addr, clit_addr;
	socklen_t	clit_addr_len;

	bzero(&serv_addr, sizeof(serv_addr));//将地址空间清零，即初始化，也可以用memset函数
	serv_addr.sin_family = AF_INET;
	serv_addr.sin_port = htons(SERV_PORT);//本地字节序转网络字节序
	serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);//任意可用ip地址

	lfd = socket(AF_INET,SOCK_STREAM,0);
	if(lfd == -1)
		sys_err("server socket create error");
	

	//设置端口复用
	int opt = 1;
	int ret = setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR,&opt, sizeof(opt));
	if(ret == -1)
		sys_err("setsocketopt  error");
	

	ret = bind(lfd,(struct sockaddr *)&serv_addr,sizeof(serv_addr));
	if(ret == -1)
		sys_err("server listen error");
	
	ret = listen(lfd, 128);
	if(ret == -1){
		sys_err("server listen error");
	}

	fd_set rset, allset;
	maxfd = lfd;
	
	for (i=0;i<1024;i++)
		client[i] = -1;//初始化client数组

	FD_ZERO(&allset);
	FD_SET(lfd, &allset);

	while(1){
		rset = allset; //rset表示实际有那些请求，allset表示我想监听那些请求
		nready = select(maxfd+1, &rset, NULL, NULL, NULL);
		if (nready <0)
			sys_err("select error");
		
		if (FD_ISSET(lfd,&rset)){//上来先判断客户端是否向lfd发数据，有就调accept建立连接	

			clit_addr_len = sizeof(clit_addr);//传入只用求一次，放在循环里会反复求
			cfd = accept(lfd, (struct sockaddr *)&clit_addr, &clit_addr_len) ;
			if(cfd == -1)
				sys_err("server accept error");
			
			printf("client %d connected\n", cfd);
			printf("client ip:%s, port:%d\n", 
					inet_ntop(AF_INET, &clit_addr.sin_addr.s_addr, clit_ip, sizeof(clit_ip)),
					ntohs(clit_addr.sin_port));

			for (i=0; i<1024; i++){
				if (client[i]<0){
					client[i] = cfd;//将新建的cfd加入数组
					break;
				}
			}
			
			if (i == 1024){
				printf("too much clients\n");
				exit(1);
			}

			FD_SET(cfd,&allset); //将新创建的cfd加入select监听队列
			if (maxfd < cfd)
				maxfd = cfd;

			if (i > maxi)
				maxi = i; //让maxi始终保存client[]最后一个元素的下标

			if (nready == 1)
			continue; //说明只有listen,后面对客户端读写操作不用执行
		}

		for (i = 0; i <= maxi; i++){//这里的i是数组下标了，不是文件描述符了
			if(client[i] < 0)
				continue;//client[]初始化全是-1，client[i]<0表示没有文件描述符进入client数组，所以后面不用执行
			if (FD_ISSET(client[i], &rset)){  //检测那个cfd对客户端发送了数据
				
	    		fret = read(client[i],buf,sizeof(buf));
				if(fret == 0){
					printf("client %d closed\n", client[i]);
					close(client[i]);
					FD_CLR(client[i],&allset); //客户端已经断开连接，故从select中移除
					client[i] = -1;//还要将数组中元素复原
				}

				write(STDOUT_FILENO, buf ,fret);

				for(j = 0; j<fret; j++){
					buf[j] = toupper(buf[j]);//对数据进行操作			
				}
				write(client[i],buf,fret);
			}

			if (nready == 1)//表明只有一个cfd，处理完就结束了
				break;//跳出for，但还在while中
		//因为select返回1有可能是lfd,也有可能是一个cfd，所以上面和这里都进行判断，并且一个用contiue，一个用break.
		}
	}
	close(lfd);
	return 0;
}

